<!DOCTYPE html>
<html>

<head>
    <title>棋谱复盘</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.staticfile.net/twitter-bootstrap/5.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.staticfile.net/twitter-bootstrap/5.1.1/js/bootstrap.bundle.min.js"></script>
    <style>
        body {
            background-repeat: no-repeat;
        }
    </style>
</head>

<body>
    <div class="container input-group mt-2">
        <div id="baseid" class="btn-group-vertical" style="margin: auto;">
            <div class="input-group">
                <button type="button" class="form-control btn btn-primary" onclick="tgo.newGo()">New</button>
                <button type="button" class="form-control btn btn-primary" onclick="tgo.show()">Show</button>
            </div>
            <div class="input-group">
                <button type="button" class="btn btn-primary" onclick="tgo.xgShow('-')">-</button>
                <input class="form-control" type="range" id="myRange" onchange="tgo.xgShow()" value="0" min="0" max="0"
                    step="1">
                <button type="button" class="btn btn-primary" onclick="tgo.xgShow('+')">+</button>
            </div>
            <div class="input-group">
                <button type="button" class="form-control btn btn-primary" onclick="tgo.mode(event)">记录</button>
                <span id="posColorList" class="badge bg-danger btn" style="width: 50px;"
                    onclick="tgo.xgShow('A', event)">0</span>
                <button id="qp-mode" type="button" class="form-control btn btn-primary"
                    onclick="tgo.mode(event)">下棋模式</button>
            </div>
            <div style="margin: auto;">
                <canvas id="myCanvas" style="border:1px solid gray;background-color: rgba(255, 255, 0, 0.2);"></canvas>
            </div>
            <textarea class="form-control" id="comment"></textarea>
        </div>
    </div>
    <script>
        var health_alert = false;
        function health() {
            const now = new Date();
            const minutes = now.getMinutes();
            if (minutes === 0 || minutes === 30) {
                if (health_alert) {
                    alert("游戏健康忠告：请注意休息，合理安排游戏时间。");
                    health_alert = false;
                }
            } else {
                health_alert = true;
            }
            setTimeout("health()", 10000);
        }
        setTimeout("health()", 1000);
        window.addEventListener('resize', function () {
            document.getElementById("baseid").clientWidth = document.getElementById("myCanvas").clientWidth
        });
        class simpleGo {
            constructor() {
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.newWeiQi([], {}, []);
                this.goString = { string: [], empty: [] };
            }
            go_handler(item = "B[qd]") {
                var position = item.slice(2, 4);
                if (this.posColor.hasOwnProperty(position)) {
                    return false;
                } else {
                    this.posColor[position] = item[0];
                }
                var del_pos = [];
                var near_pos = this.near_positions(position);
                for (var npn in near_pos) {
                    if (this.posColor.hasOwnProperty(near_pos[npn])) {
                        if (this.posColor[near_pos[npn]] != this.posColor[position]) {
                            this.goString = { 'string': [], 'empty': [] };
                            this.go_string(near_pos[npn]);
                            if (this.goString['empty'].length == 0) {
                                for (var spn in this.goString['string']) {
                                    del_pos.includes(this.goString['string'][spn]) || del_pos.push(this.goString['string'][spn]);
                                }
                            }
                        }
                    }
                }
                if (del_pos.length == 0) {
                    this.goString = { 'string': [], 'empty': [] };
                    this.go_string(position);
                    if (this.goString['empty'].length == 0) {
                        delete this.posColor[position];
                        return false;
                    }
                } else {
                    for (var d_p in del_pos) {
                        delete this.posColor[del_pos[d_p]];
                    }
                }
                this.sgf.push(item);
                this.posColorList.push(JSON.parse(JSON.stringify(this.posColor)));
                return true;
            }
            go_string(position) {
                this.goString['string'].push(position);
                var nPos = this.near_positions(position);
                for (var np in nPos) {
                    if (this.posColor.hasOwnProperty(nPos[np])) {
                        if (!this.goString['string'].includes(nPos[np]) && this.posColor[nPos[np]] == this.posColor[position]) {
                            this.go_string(nPos[np]);
                        }
                    } else {
                        this.goString['empty'].includes(nPos[np]) || this.goString['empty'].push(nPos[np]);
                    }
                }
            }
            near_positions(position) {
                var near_pos = [];
                var row = this.near(position[0]);
                var col = this.near(position[1]);
                for (var r in row) {
                    near_pos.push(row[r] + position[1]);
                }
                for (var c in col) {
                    near_pos.push(position[0] + col[c]);
                }
                return near_pos;
            }
            near(char) {
                switch (char) {
                    case "a":
                        return ["b"];
                    case "s":
                        return ["r"];
                    case "b": case "c": case "d": case "e": case "f": case "g":
                    case "h": case "i": case "j": case "k": case "l": case "m":
                    case "n": case "o": case "p": case "q": case "r":
                    default:
                        var pos = this.positions.indexOf(char);
                        return [this.positions[pos - 1], this.positions[pos + 1]];
                }
            }
            newWeiQi(sgf, posColor, posColorList) {
                this.sgf = sgf;
                this.posColor = posColor;
                this.posColorList = posColorList;
            }
        }
        const simplego = new simpleGo();

        const sgftext = `(;CA[UTF-8]AP[YuanYu]GM[1]FF[4]
SZ[19]
GN[2018腾讯世界人工智能围棋大赛决赛第1局 30日10：00]
DT[2018-07-30]
PB[Golaxy]BR[9d]
PW[FineArt]WR[9d]
KM[7.5]HA[0]RU[Chinese]RE[W+R]TM[3600]TC[10]TT[60]
;B[qd];W[pp];B[dd];W[dp];B[qn];W[qo];B[pn];W[np];B[pj];W[od];B[lc]
;W[me];B[lq];W[qe];B[fq];W[cc];B[qq];W[rn];B[cd];W[dc];B[ed];W[ec]
;B[rm];W[or];B[ro];W[rp];B[qp];W[po];B[sn];W[rq];B[fc];W[fb];B[cn]
;W[er];B[bc];W[bb];B[eb];W[db];B[gb];W[pd];B[eo];W[co];B[bn];W[fp]
;B[ep];W[eq];B[do];W[cq];B[fr];W[iq];B[hp];W[gq];B[gp];W[fo];B[fn]
;W[go];B[hn];W[gr];B[gn];W[fs];B[ei];W[bd];B[be];W[ac];B[cf];W[ih]
;B[qc];W[rf];B[pf];W[qh];B[ob];W[nb];B[oc];W[pc];B[pb];W[rd];B[nd]
;W[qb];B[oe];W[rc];B[nc];W[if];B[hg];W[ig];B[je];W[gi];B[fg];W[hf]
;B[fj];W[lg];B[lf];W[mg];B[kg];W[kh];B[mf];W[ff];B[gg];W[fd];B[gc]
;W[hd];B[fe];W[gd];B[ic];W[gj];B[kf];W[bg];B[cg];W[kj];B[bo];W[gk]
;B[fk];W[id];B[jc];W[eh];B[fh];W[fi];B[ej];W[fl];B[el];W[gl];B[lo]
;W[ae];B[bh];W[em];B[dl];W[km];B[ho];W[qk];B[ol];W[bf];B[ce];W[gf]
;B[ef];W[qj];B[hq];W[hr];B[pi];W[qi];B[lk];W[kk];B[lh];W[li];B[mh]
;W[ng];B[oo];W[pq];B[og];W[nh];B[mi];W[oh];B[qr];W[rr];B[mj];W[ph]
;B[lm];W[kl];B[jh];W[ki];B[bq];W[hh];B[eg];W[ll];B[ml];W[no];B[ln]
;W[nk];B[mk];W[nm];B[nn];W[mm];B[nl];W[mn];B[om];W[mo];B[jq];W[rl]
;B[ql];W[pl];B[qm];W[ir];B[jn];W[pk];B[rk];W[on];B[pm];W[rj];B[sl]
;W[cr];B[jg];W[ji];B[hl];W[il];B[hm];W[jp];B[kp];W[kn];B[ko];W[ik]
;B[cp];W[jr];B[jo];W[hc];B[hb];W[ea];B[af];W[ag];B[fq];W[br];B[ar]
;W[lr];B[mr];W[kq];B[mq];W[ad];B[ah];W[af];B[fm];W[sp];B[nn];W[op]
;B[dq];W[dr];B[jm];W[jl];B[ls];W[on];B[kr];W[nf];B[ne];W[ok];B[hk]
;W[hj];B[pg];W[jd])`
        class extract_go_manual {
            constructor() {
                this.colors = ["B", "W"];
                this.positions = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"];
                this.combinedPattern = /[\u4e00-\u9fa5a-zA-Z0-9;\[\]\-： ]+/g;
                this.sgf = [];
            }
            handle(text) {
                this.sgf = [];
                var results = text.match(this.combinedPattern) || [];
                var format_content = results.join('').split(";");
                for (var index in format_content) {
                    var item = format_content[index];
                    if (item.length == 5 && this.colors.includes(item[0])) {
                        if (this.positions.includes(item[2]) && this.positions.includes(item[3])) {
                            this.sgf.push(item);
                        }
                    }
                }
            }
            sgfCanvas(sgf = this.sgf) {
                simplego.sgf = [];
                simplego.posColor = {};
                simplego.posColorList = [];
                for (var index in sgf) {
                    simplego.go_handler(sgf[index]);
                }
                qipan.goStep();
            }
        }
        const egm = new extract_go_manual();

        class qipanDraw {
            constructor(myCanvas, grid = 20) {
                this.grid = grid;
                this.strokeStyle = "B";
                this.myCanvas = document.getElementById(myCanvas);
                this.myCanvas.width = 20 * this.grid;
                this.myCanvas.height = 20 * this.grid;
                document.getElementById("baseid").width = this.myCanvas.width
                this.setupClickListener(this.myCanvas, this);
                this.ctx = this.myCanvas.getContext("2d");
                this.ctxShow();
                this.painting = false;
                this.blueList = [];
            }
            ctxShow(posColor = simplego.posColor) {
                this.ctxGridLine();
                let strokeStyle = { B: "black", W: "White" };
                for (var pos in posColor) {
                    var x = (simplego.positions.indexOf(pos[0]) + 1) * this.grid;
                    var y = (simplego.positions.indexOf(pos[1]) + 1) * this.grid;
                    this.ctx.moveTo(x + Math.floor(this.grid / 2 - 1), y);
                    if (pos == simplego.sgf[document.getElementById("myRange").value].slice(2, 4)) {
                        this.ctxArc(x, y, strokeStyle[posColor[pos]], "Gold", true, 3);
                    } else if (this.blueList.includes(pos)) {
                        this.ctxArc(x, y, strokeStyle[posColor[pos]], "LightBlue", true, 3);
                    } else {
                        this.ctxArc(x, y, strokeStyle[posColor[pos]], "black", true, 2);
                    }
                }
                this.ctx.strokeStyle = "black";
            }
            ctxGridLine() {
                this.ctx.clearRect(0, 0, this.myCanvas.width, this.myCanvas.height);
                let start_x, start_y, end_x, end_y;
                this.ctx.beginPath();
                this.ctx.strokeStyle = "black";
                this.ctx.lineWidth = 2;
                for (let x = 1; x < 20; x++) {
                    [start_x, start_y, end_x, end_y] = [x * this.grid, this.grid, x * this.grid, 19 * this.grid];
                    this.ctx.moveTo(start_x, start_y);
                    this.ctx.lineTo(end_x, end_y);
                    [start_x, start_y, end_x, end_y] = [this.grid, x * this.grid, 19 * this.grid, x * this.grid];
                    this.ctx.moveTo(start_x, start_y);
                    this.ctx.lineTo(end_x, end_y);
                }
                this.ctx.stroke();
                let a = [4 * this.grid, 10 * this.grid, 16 * this.grid];
                for (let x in a) {
                    for (let y in a) {
                        this.ctxArc(a[x], a[y], 'black', 'black', false, 1, Math.floor(this.grid / 4));
                    }
                }
            }
            ctxArc(x, y, fColor = 'black', sColor = 'black', fill = true, lWidth = 1, r = Math.floor(this.grid / 2 - 1)) {
                this.ctx.beginPath();
                this.ctx.strokeStyle = sColor;
                this.ctx.lineWidth = lWidth;
                this.ctx.arc(x, y, r, 0, 2 * Math.PI);
                if (fill) {
                    this.ctx.fillStyle = fColor;
                    this.ctx.fill();
                }
                this.ctx.stroke();
            }
            setupClickListener(element, someVariable) {
                element.addEventListener('mousedown', function (event) {
                    someVariable.sgo("mousedown", event.clientX, event.clientY);
                });
                element.addEventListener('mousemove', function (event) {
                    someVariable.sgo("mousemove", event.clientX, event.clientY);
                });
                element.addEventListener('mouseup', function (event) {
                    someVariable.sgo("mouseup", event.clientX, event.clientY);
                });
            }
            sgo(action, clientX, clientY) {
                let qpMode = document.getElementById("qp-mode").innerText;
                var rect = this.myCanvas.getBoundingClientRect();
                let row, col, ww;
                ww = this.grid / 2;
                row = Math.floor((clientX - rect.left - ww) / this.grid);
                col = Math.floor((clientY - rect.top - ww) / this.grid);
                if (action == "mousedown") {
                    tgo.autoRun = false;
                    let pcid = document.getElementById("posColorList");
                    pcid.classList.remove("bg-success");
                    pcid.classList.add("bg-danger");
                }
                if (qpMode == "下棋模式" && action == "mousedown") {
                    let sgf = this.strokeStyle + "[" + simplego["positions"][row] + simplego["positions"][col] + "]";
                    if (simplego.go_handler(sgf)) {
                        qipan.goStep();
                    }
                } else if (qpMode == "选择模式" && action == "mousedown") {
                    [this.startX, this.startY] = [row, col];
                    this.painting = true;
                    this.selectedList = [[row, col]];
                    this.ctx.strokeStyle = "red";
                    this.ctx.lineWidth = 2;
                    this.ctx.beginPath();
                    this.ctx.moveTo((row + 1) * this.grid, (col + 1) * this.grid);
                } else if (qpMode == "选择模式" && action == "mousemove") {
                    if (this.painting) {
                        if (!this.arraysAreEqual([row, col], this.selectedList[this.selectedList.length - 1])) {
                            this.selectedList.push([row, col]);
                            this.ctx.lineTo((row + 1) * this.grid, (col + 1) * this.grid);
                            this.ctx.stroke();
                        }
                    }
                } else if (qpMode == "选择模式" && action == "mouseup") {
                    this.painting = false;
                    if (!this.arraysAreEqual(this.selectedList[0], this.selectedList[this.selectedList.length - 1])) {
                        this.selectedList.push(this.selectedList[0]);
                    }
                    this.blueList = [];
                    let r, c, linshiList = [];
                    simplego.sgf.forEach((item, index) => {
                        r = simplego.positions.indexOf(item.slice(2, 3));
                        c = simplego.positions.indexOf(item.slice(3, 4));
                        if (this.isPointInPolygon([r, c], this.selectedList)) {
                            linshiList.push([simplego.positions.indexOf(item.slice(2, 3)), simplego.positions.indexOf(item.slice(3, 4))]);
                        }
                    });
                    this.selectedList.concat(linshiList).forEach((item, index) => {
                        let pos = simplego.positions[item[0]] + simplego.positions[item[1]];
                        if (!this.blueList.includes(pos)) {
                            this.blueList.push(pos);
                        }
                    });
                    let sgf = [], pcDict = {}, pcList = [];
                    simplego.sgf.forEach((item, index) => {
                        if (this.blueList.includes(item.slice(2, 4))) {
                            sgf.push(item);
                            pcList.push(JSON.parse(JSON.stringify(simplego.posColorList[index])));
                        }
                    });
                    pcDict = JSON.parse(JSON.stringify(pcList[pcList.length - 1]));
                    simplego.newWeiQi(sgf, pcDict, pcList);
                    qipan.goStep();
                    tgo.autoRun = true;
                    let pcid = document.getElementById("posColorList");
                    pcid.classList.remove("bg-danger");
                    pcid.classList.add("bg-success");
                    setTimeout("tgo.autorunning(0)", 2000);
                }
            }
            goStep() {
                let listLength = simplego.posColorList.length - 1;
                document.getElementById("myRange").max = listLength;
                document.getElementById("myRange").value = listLength;
                document.getElementById("posColorList").innerHTML = listLength;
                if (simplego.sgf.length > 0 && simplego.sgf[simplego.sgf.length - 1].slice(0, 1) == "B") {
                    this.strokeStyle = "W";
                } else {
                    this.strokeStyle = "B";
                }
                this.ctxShow();
            }
            rectangle(startX, startY, currentX, currentY, pc = simplego.posColor, strokeStyle = 'red') {
                this.ctx.clearRect(0, 0, this.myCanvas.width, this.myCanvas.height);
                this.ctxShow(pc);
                this.ctx.strokeStyle = strokeStyle;
                const x = Math.min(startX, currentX);
                const y = Math.min(startY, currentY);
                var width = Math.max(startX, currentX) - Math.min(startX, currentX);
                var height = Math.max(startY, currentY) - Math.min(startY, currentY);
                this.ctx.beginPath();
                this.ctx.lineTo(x, y);
                this.ctx.lineTo(x + width, y);
                this.ctx.lineTo(x + width, y + height);
                this.ctx.lineTo(x, y + height);
                this.ctx.closePath();
                this.ctx.stroke();
            }
            line(startX, startY, currentX, currentY, text = "", strokeStyle = 'black') {
                this.ctx.strokeStyle = strokeStyle;
                this.ctx.beginPath();
                this.ctx.moveTo(startX, startY);
                this.ctx.lineTo(currentX, currentY);
                this.ctx.stroke();
            }
            arraysAreEqual(arr1, arr2) {
                if (arr1.length !== arr2.length) {
                    return false;
                }
                for (let i = 0; i < arr1.length; i++) {
                    if (arr1[i] !== arr2[i]) {
                        return false;
                    }
                }
                return true;
            }
            isPointInPolygon(point, polygon) {
                let numIntersections = 0;
                const x = point[0];
                const y = point[1];
                const n = polygon.length;
                for (let i = 0, j = n - 1; i < n; j = i++) {
                    const xi = polygon[i][0], yi = polygon[i][1];
                    const xj = polygon[j][0], yj = polygon[j][1];
                    if ((yi > y) != (yj > y) &&
                        x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
                        numIntersections++;
                    }
                }
                return numIntersections % 2 !== 0;
            }
        }
        var qipan = new qipanDraw('myCanvas', 30);

        class toolsGo {
            constructor(className) {
                this.className = className;
                this.autoRun = false;
            }
            show() {
                let text = document.getElementById("comment").value;
                if (text != "") {
                    egm.handle(text);
                } else {
                    egm.handle(sgftext);
                }
                egm.sgfCanvas();
            }
            newGo() {
                simplego.sgf = [];
                simplego.posColor = {};
                simplego.posColorList = [];
                qipan.ctxShow();
                document.getElementById("myRange").max = 0;
                document.getElementById("myRange").value = 0;
                document.getElementById("posColorList").innerHTML = 0;
            }
            newShow(sPos = document.getElementById("myRange").value) {
                qipan.ctxShow(simplego.posColorList[Number(sPos)]);
                document.getElementById("posColorList").innerHTML = sPos;
            }
            xgShow(xiugai, e = null) {
                let sPos = Number(document.getElementById("myRange").value);
                switch (xiugai) {
                    case "-":
                        sPos = Math.max(0, sPos - 1);
                        break;
                    case "+":
                        sPos = Math.min(simplego.posColorList.length - 1, sPos + 1);
                        break;
                    case "A":
                        if (this.autoRun) {
                            this.autoRun = false;
                            e.target.classList.remove("bg-success");
                            e.target.classList.add("bg-danger");
                        } else {
                            this.autoRun = true;
                            e.target.classList.remove("bg-danger");
                            e.target.classList.add("bg-success");
                            setTimeout("tgo.autorunning(0)", 2000);
                        }
                        break;
                    default:
                        sPos = Number(document.getElementById("myRange").value);
                        break;
                }
                document.getElementById("myRange").value = sPos;
                //document.getElementById("posColorList").innerHTML = sPos;
                this.newShow(sPos);
            }
            autorunning(n) {
                if (n < simplego.posColorList.length) {
                    document.getElementById("myRange").value = n;
                    this.newShow(n++);
                    this.autoRun && setTimeout("tgo.autorunning(" + n + ")", 2000);
                } else {
                    this.autoRun = false;
                    let pcid = document.getElementById("posColorList");
                    pcid.classList.remove("bg-success");
                    pcid.classList.add("bg-danger");
                }
            }
            mode(e) {
                if (e.target.textContent == "下棋模式") {
                    e.target.textContent = "选择模式";
                    qipan.blueList = [];
                } else if (e.target.textContent == "选择模式") {
                    e.target.textContent = "下棋模式";
                } else if (e.target.textContent == "记录") {
                    e.target.textContent = "返回";
                    this.guDingData = JSON.parse(JSON.stringify([simplego.sgf, simplego.posColor, simplego.posColorList]));
                    let newLength = Number(document.getElementById("myRange").value);
                    simplego.sgf = JSON.parse(JSON.stringify(simplego.sgf.slice(0, newLength + 1)));
                    simplego.posColorList = JSON.parse(JSON.stringify(simplego.posColorList.slice(0, newLength + 1)));
                    simplego.posColor = JSON.parse(JSON.stringify(simplego.posColorList[newLength]));
                    qipan.goStep();
                } else if (e.target.textContent == "返回") {
                    e.target.textContent = "记录";
                    simplego.sgf = JSON.parse(JSON.stringify(this.guDingData[0]));
                    simplego.posColor = JSON.parse(JSON.stringify(this.guDingData[1]));
                    simplego.posColorList = JSON.parse(JSON.stringify(this.guDingData[2]));
                    qipan.goStep();
                }
            }
        }
        const tgo = new toolsGo("tgo");
    </script>

</body>

</html>